#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include <glib.h>

#include <dirent.h>
#include <cstring>

#include <deque>

#include "sound.h"
#include "global.h"
#include "util.h"

GstElement* playbin = NULL;
deque<Track> tracks;

static gboolean gst_message (GstBus* bus, GstMessage* msg, gpointer data) {
  //GMainLoop* loop = (GMainLoop*) data;

  switch (GST_MESSAGE_TYPE (msg)) {

    case GST_MESSAGE_EOS:
      //g_print ("End of stream\n");
      /* g_main_loop_quit (loop); */
			next_track();
      break;

    case GST_MESSAGE_ERROR: {
      gchar* debug;
      GError* error;

      gst_message_parse_error (msg, &error, &debug);
      g_free (debug);

      g_printerr ("Error: %s\n", error->message);
      g_error_free (error);

      /* g_main_loop_quit (loop); */
      break;
    }
#if 0
		// This is replaced by Track::set_tags()
    case GST_MESSAGE_TAG: {
      /* http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gstreamer-GstTagList.html */
      GstTagList* tags = NULL;
      gchar* title = NULL;

      gst_message_parse_tag(msg, &tags);
      g_print("Tags from %s\n", GST_OBJECT_NAME(msg->src));

      gst_tag_list_get_string(tags, GST_TAG_TITLE, &title);
      g_print("Title: %s\n", title);
      g_free(title);
      gst_tag_list_free(tags);
      break;
    }
#endif
    default: break;
  }

  return TRUE;
}

int init_sound(int* argc, char** argv) {
	GstBus* bus;
	GstElement* fakesink;

	gst_init(argc, &argv);
	fakesink = gst_element_factory_make("fakesink", "sink");
  playbin = gst_element_factory_make("playbin", "play");
	g_object_set(G_OBJECT(playbin), "video-sink", fakesink, NULL);

  bus = gst_pipeline_get_bus (GST_PIPELINE (playbin));
  gst_bus_add_watch (bus, gst_message, NULL);
  gst_object_unref (bus);
	return 0;
}

void load_tracks(string dirn) {
	DIR* dir = opendir(dirn.c_str());
	struct dirent* dent = NULL;
	deque<Track>::iterator it;

	Track t;

	while ((dent = readdir(dir))) {
		if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
			continue;
		t.index = tracks.size();
		t.filename = dirn + "/" + dent->d_name;
		t.set_tags();
		tracks.push_front(t);
	}
}

void print_tracks() {
	deque<Track>::iterator it;
	for (it = tracks.begin(); it != tracks.end(); it ++) {
		printf("%s\n", (*it).track.c_str());
	}
}


#ifdef OLD_TAGS
typedef struct old_tag_arg {
	GMainLoop* loop;
	Track* track;
} TagArg;

static gboolean tag_msg(GstBus* bus, GstMessage* msg, gpointer data) {
	TagArg* arg = (TagArg*)data;
	Track* current = arg->track;
	char* str = NULL;
	bool tagged = FALSE;

	switch (GST_MESSAGE_TYPE(msg)) {
		case GST_MESSAGE_TAG: {
      GstTagList* tags = NULL;

      gst_message_parse_tag(msg, &tags);
			if (gst_tag_list_get_string(tags, GST_TAG_TITLE, &str)) {
				current->track = str;
				printf("Title: %s\n", str);
				tagged = TRUE;
			}
			if (gst_tag_list_get_string(tags, GST_TAG_ARTIST, &str)) {
				current->artist = str;
				printf("Artist: %s\n", str);
				tagged = TRUE;
			}
			if (gst_tag_list_get_string(tags, GST_TAG_ALBUM, &str)) {
				current->album = str;
				printf("Album: %s\n", str);
				tagged = TRUE;
			}
      gst_tag_list_free(tags);
      break;
		}
		default: break;
	}
	if (tagged) {
		g_main_loop_quit(arg->loop);
	}
	return !tagged;
}
#endif

void Track::set_tags() {
	char* str = gst_filename_to_uri(this->filename.c_str(), NULL);
#ifdef OLD_TAGS
	puts("OLD_TAGS in use");
	TagArg ta;

	/* Context stuff will only work correctly with gstreamer 0.10.22 and higher */
	GMainContext* context = g_main_context_new();
	GMainLoop* loop = g_main_loop_new(context, FALSE);
	GSource* source;

	GstElement* tagreader = gst_element_factory_make("playbin", "tags");
	GstElement* fakesink = gst_element_factory_make("fakesink", "fsink");
	GstBus* bus;

	g_object_set(G_OBJECT(tagreader), "audio-sink", fakesink, NULL);
	g_object_set(G_OBJECT(tagreader), "video-sink", fakesink, NULL);
	g_object_set(G_OBJECT(tagreader), "uri", str, NULL);

	/* Watch only the events of the new playbin */
	bus = gst_pipeline_get_bus(GST_PIPELINE(tagreader));
	source = gst_bus_create_watch(bus);
	ta.track = this;
	ta.loop = loop;
	g_source_set_callback(source, (GSourceFunc)tag_msg, &ta, NULL);
	g_source_attach(source, context);
	g_source_unref(source);
	g_main_context_unref(context);

	gst_object_unref(bus);
	gst_element_set_state(tagreader, GST_STATE_PAUSED);

	g_main_loop_run(loop);
	g_main_loop_unref(loop);
#else

	GstDiscoverer* discover = gst_discoverer_new(GST_SECOND * 10, NULL);
#if !(GST_CHECK_VERSION(0, 10, 36))
	// Workarond bug #652838
	GError* err;
	GstDiscovererInfo* info = gst_discoverer_discover_uri(discover, str, &err);
#else
	GstDiscovererInfo* info = gst_discoverer_discover_uri(discover, str, NULL);
#endif
	GstTagList* tags = (GstTagList*)gst_discoverer_info_get_tags(info);

	gst_tag_list_get_string(tags, GST_TAG_TITLE, &str);
	this->track = str;
	g_free(str);
	gst_tag_list_get_string(tags, GST_TAG_ARTIST, &str);
	this->artist = str;
	g_free(str);
	gst_tag_list_get_string(tags, GST_TAG_ALBUM, &str);
	this->album = str;
	g_free(str);

	gst_tag_list_free(tags);
#endif
}

void kill_sound() {
	if (playbin) {
		gst_element_set_state(playbin, GST_STATE_NULL);
		gst_object_unref(GST_OBJECT(playbin));
	}
}

static void track_head() {
	Track t = tracks.front();
	bool play = FALSE;

	if (play_state() == GST_STATE_PLAYING) {
		gst_element_set_state(playbin, GST_STATE_NULL);
		play = TRUE;
	}

	g_object_set(G_OBJECT(playbin), "uri", gst_filename_to_uri(t.filename.c_str(), NULL), NULL);
	printf("Selected %s - %s [%s]\n", t.artist.c_str(), t.track.c_str(), t.album.c_str());
	if (play) {
		play_pause();
	}
}

GstStateChangeReturn play_pause() {
	char* uri;
	GstState state;
	
	if (!tracks.size()) {
		return GST_STATE_CHANGE_FAILURE;
	}
	g_object_get(G_OBJECT(playbin), "uri", &uri, NULL);
	if (!uri) {
		track_head();
	}
	state = play_state();

	if (state == GST_STATE_PLAYING) {
		return gst_element_set_state(playbin, GST_STATE_PAUSED);
	}
	else {
		return gst_element_set_state(playbin, GST_STATE_PLAYING);
	}
}

GstState play_state() {
	GstState state, pending;
	gst_element_get_state(playbin, &state, &pending, GST_SECOND);
	if (pending != GST_STATE_VOID_PENDING)
		return pending;
	else
		return state;
}

void next_track() {
	Track t = tracks.front();
	tracks.pop_front();
	tracks.push_back(t);
	track_head();
}

void prev_track() {
	Track t = tracks.back();
	tracks.pop_back();
	tracks.push_front(t);
	track_head();
}

/* vim:set ts=2 sts=2 sw=2 noexpandtab: */
